/*jslint onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true,
         bitwise: true, regexp: true, newcap: true, immed: true, strict: true,
         white: true, indent: 2, maxlen: 80 */
/*global DOMException, JSON, encodeURIComponent, window */
(function () {
  "use strict";

  /**
   * Calculates the (top, left) absolute CSS position of the given element.
   */
  function locate(element) {
    if (element === element.ownerDocument.body) {
      return {
        top:  element.ownerDocument.body.offsetTop,
        left: element.ownerDocument.body.offsetLeft
      };
    }
    var box = element.getBoundingClientRect(),
      doc = element.ownerDocument,
      body = doc.body,
      docElem = doc.documentElement,
      clientTop = docElem.clientTop || body.clientTop || 0,
      clientLeft = docElem.clientLeft || body.clientLeft || 0;
    return {
      top: box.top +
           (window.pageYOffset || docElem.scrollTop || body.scrollTop) -
           clientTop,
      left: box.left +
            (window.pageXOffset || docElem.scrollLeft || body.scrollLeft) -
            clientLeft
    };
  }

  function makeNodeRemover(dialog) {
    return function () {
      if (dialog.parentNode) {
        dialog.parentNode.removeChild(dialog);
      }
    };
  }

  if (!window.introducer) {
    if (window.webkitIntroducer) {
      // Use the pending WebKit implementation.
      window.introducer = window.webkitIntroducer;
    } else if (!window.JSON || !window.postMessage || !window.localStorage) {
      // User-agent does not support APIs needed for mock implementation.
      window.introducer = {
        install : function () {},
        uninstall : function () {},
        offer : function () {},
        rescind : function () {},
        connect: function (anchor, dialog, wanted, callback) {
          callback(null, [], false);
        },
        accept : function () {}
      };
    } else {
      // Eventually, the user-agent will have a built-in implementation of the
      // Introducer API with better security and UX. In the meantime, this
      // default implementation uses an IFRAME on a globally trusted site.
      window.introducer = (function () {
        // This code runs in either a Client or Service page, so each can
        // send any request to the Introducer IFRAME and has full access to all
        // received data.  Consequently, the postMessage interface to an
        // Introducer IFRAME is designed to not reveal any information about
        // installations and does not rely on the behavior of either the
        // Client or the Service.
        var introducerOrigin = 'https://webintroducer.net',
          introducerURL = introducerOrigin + '/',
          pseudoPorts = []; // MessagePorts implemented on Window postMessage

        // Implement MessagePort on top of IFRAME postMessage for legacy comm.
        function startPseudoPort(target) {
          var listeners = [],
            port = {
              addEventListener: function (type, listener) {
                var i;
                if ('message' !== type) {
                  return;
                }
                for (i = 0; i < listeners.length; i += 1) {
                  if (listeners[i] === listener) {
                    return;
                  }
                }
                listeners.push(listener);
              },
              removeEventListener: function (type, listener) {
                var i;
                if ('message' !== type) {
                  return;
                }
                for (i = 0; i < listeners.length; i += 1) {
                  if (listeners[i] === listener) {
                    listeners.splice(i, 1);
                    break;
                  }
                }
              },
              dispatchEvent: function (msg) {
                var i;
                if ('message' !== msg.type) {
                  return;
                }
                if (this.onmessage) {
                  this.onmessage(msg);
                }
                for (i = 0; i !== listeners.length; i += 1) {
                  listeners[i].apply(this, [ msg ]);
                }
                return true;
              },
              postMessage: function (message, ports) {
                if (ports && ports.length) {
                  throw new DOMException(DOMException.INVALID_STATE_ERR);
                }
                target.postMessage(JSON.stringify(message), introducerOrigin);
              },
              close: function () {
                var i;
                target = null;
                for (i = 0; i < pseudoPorts.length; i += 1) {
                  if (pseudoPorts[i].port === this) {
                    if (pseudoPorts[i].onclose) {
                      pseudoPorts[i].onclose();
                    }
                    pseudoPorts.splice(i, 1);
                    break;
                  }
                }
              },
              start: function () {}
            };
          pseudoPorts.push({
            source: target,
            port: port
          });
          return port;
        }
        function onPseudoPortClose(port, action) {
          var i;
          for (i = 0; i < pseudoPorts.length; i += 1) {
            if (pseudoPorts[i].port === port) {
              pseudoPorts[i].onclose = action;
              break;
            }
          }
        }
        function listenForMessages(listener) {
          if (window.addEventListener) {
            window.addEventListener('message', listener, false);
          } else if (window.attachEvent) {
            window.attachEvent('onmessage', listener);
          }
        }
        listenForMessages(function (msg) {
          var i;
          if (msg.origin !== introducerOrigin) {
            return;
          }
          for (i = 0; i < pseudoPorts.length; i += 1) {
            if (pseudoPorts[i].source === msg.source) {
              pseudoPorts[i].port.dispatchEvent({
                type: 'message',
                ports: msg.ports,
                data: msg.data ? JSON.parse(msg.data) : undefined
              });
              break;
            }
          }
        });
        function onload(iframe, callback) {
          var once = function () {
            var tmp = callback;
            callback = undefined;
            if (tmp) {
              tmp();
            }
          };
          iframe.onload = once;

          // For compatibility with IE.
          iframe.onreadystatechange = function () {
            if ('complete' === iframe.readyState) {
              once();
            }
          };
        }
        function send(name, argv) {
          var doc = window.document,
            iframe = doc.createElement('iframe');
          iframe.src = introducerURL;
          iframe.scrolling = 'no';
          iframe.frameBorder = 0;
          iframe.style.borderWidth = 0;
          iframe.height = 0;
          iframe.width = 0;
          doc.body.appendChild(iframe);
          onload(iframe, function () {
            var port = startPseudoPort(iframe.contentWindow);
            port.onmessage = function () {
              port.onmessage = null;
              port.close();
              if (iframe.parentNode) {
                iframe.parentNode.removeChild(iframe);
              }
            };
            port.postMessage({ name: name, argv: argv });
          });
        }
        return {
          // Enable clients to detect this is a simulated implementation.
          isSimulated: true,

          // Create or update a content Service registration.
          install: function (icon, name, home, callback) {
            var doc = window.document,
              iframe = doc.createElement('iframe');
            iframe.src = introducerURL;
            iframe.scrolling = 'no';
            iframe.frameBorder = 0;
            iframe.style.borderWidth = 0;
            iframe.style.position = 'fixed';
            iframe.style.left = '0px';
            iframe.style.top = '0px';
            iframe.height = 40;
            iframe.width = '100%';
            doc.body.appendChild(iframe);
            onload(iframe, function () {
              var port = startPseudoPort(iframe.contentWindow);
              port.onmessage = function (msg) {
                port.onmessage = null;
                port.close();
                if (iframe.parentNode) {
                  iframe.parentNode.removeChild(iframe);
                }
                if (msg.data[0]) {
                  callback();
                }
              };
              port.postMessage({ name: 'install', argv: [ icon, name, home ] });
            });
          },
          uninstall: function () {
            send('uninstall', []);
          },
          offer: function (id, label, supports, window, frame) {
            send('offer', [id, label, supports, window, frame]);
          },
          rescind: function (id) {
            send('rescind', [id]);
          },

          // Ask to be connected to a particular kind of Service.
          connect: function (anchor, dialog, wanted, callback) {
            if (!anchor) {
              anchor = window.document.body;
            }
            var offsets = locate(anchor),
              iframe;
            if (dialog && 'IFRAME' === dialog.tagName) {
              // Use the provided IFRAME to connect to the Introducer.
              iframe = dialog;
              iframe.removeAttribute('sandbox');
            } else {
              // Create an IFRAME to connect to the Introducer.
              iframe = anchor.ownerDocument.createElement('iframe');
              if (anchor === anchor.ownerDocument.body) {
                anchor.appendChild(iframe);
              } else {
                anchor.parentNode.appendChild(iframe);
              }
            }
            // Clickjack the Introducer UI.
            iframe.style.opacity = 0;
            iframe.style['-ms-filter'] =
              'progid:DXImageTransform.Microsoft.Alpha(opacity=0)';
            iframe.style.filter = 'alpha(opacity=0)';
            iframe.style.borderWidth = 0;
            iframe.style.display = 'block';
            iframe.style.position = 'absolute';
            iframe.style.top = offsets.top + 'px';
            iframe.style.left = offsets.left + 'px';
            iframe.height = anchor.offsetHeight + 1;
            iframe.width = anchor.offsetWidth;
            iframe.frameBorder = 0;
            iframe.scrolling = 'no';
            iframe.src = introducerURL;
            onload(iframe, function () {
              var port = startPseudoPort(iframe.contentWindow);
              port.onmessage = function (msg) {
                port.onmessage = null;
                iframe.removeAttribute('style');
                iframe.height = 0;
                iframe.width = 0;
                if (msg.data[2]) {
                  iframe = null;
                }
                if (msg.data[0]) {
                  if (msg.ports && msg.ports.length) {
                    msg.data[0] = msg.ports[0];
                  } else {
                    if (iframe) {
                      // Need to keep the IFRAME to support messaging.
                      // This happens when the Service is loaded in a window in
                      // a browser that does not support MessageChannel.
                      iframe.style.display = 'none';
                      if (iframe !== dialog) {
                        onPseudoPortClose(port, makeNodeRemover(iframe));
                        iframe = null;
                      }
                    }
                    msg.data[0] = port;
                    port = null;
                  }
                }
                if (port) {
                  port.close();
                  port = null;
                }
                if (iframe && iframe !== dialog) {
                  if (iframe.parentNode) {
                    iframe.parentNode.removeChild(iframe);
                  }
                  iframe = null;
                }
                if (msg.data[1]) {
                  callback.apply(null, msg.data);
                }
              };
              port.postMessage({ name: 'connect', argv: [ !!dialog, wanted ] });
            });
          },

          // Inform the Introducer that this Service page can be introduced by
          // UX determined by the specified installation origin.
          accept: function (installation, callback) {
            var port = startPseudoPort(
              window.parent === window ? window.opener : window.parent
            );
            port.onmessage = function (msg) {
              // The introduction happened via an allowed UX.
              port.onmessage = null;
              if (msg.data[0]) {
                if (msg.ports && msg.ports.length) {
                  msg.data[0] = msg.ports[0];
                } else {
                  msg.data[0] = port;
                  port = null;
                }
              }
              if (port) {
                port.close();
                port = null;
              }
              if (msg.data[0]) {
                callback.apply(null, msg.data);
              }
            };
            port.postMessage({ name: 'accept', argv: [ installation ] });
          }
        };
      }());
    }
  }

  // The rest of this file contains application specific APIs built on top of
  // the Introducer API. These application specific APIs are not expected to
  // ever be natively implemented by the user-agent.

  function iterate(list, predicate) {
    var i = 0, end = list.length, found;
    for (; i < end && undefined === found; i += 1) {
      found = predicate(list[i], i);
    }
    return found;
  }

  /**
   * Produces the JSONML representation of a DOM node.
   */
  function asJSONML(element) {
    var jml;
    switch (element.nodeType) {
    case 3:   // text node
    case 4:   // CDATA node
      jml = String(element.nodeValue);
      break;
    case 1:   // element
    case 9:   // document
    case 11:  // documentFragment
      jml = [ element.tagName, {} ];
      iterate(element.attributes, function (attribute) {
        if (attribute.specified) {
          switch (attribute.name) {
          case 'action':
          case 'cite':
          case 'data':
          case 'formaction':
          case 'href':
          case 'icon':
          case 'manifest':
          case 'poster':
          case 'src':
            // Get the corresponding absolute URL.
            jml[1][attribute.name] = element[attribute.name] || attribute.value;
            break;
          case 'style':
            jml[1].style = element.style.cssText || attribute.value;
            break;
          default:
            jml[1][attribute.name] = attribute.value;
          }
        }
      });
      iterate(element.childNodes, function (node) {
        var child = asJSONML(node);
        if (undefined !== child) {
          jml.push(child);
        }
      });
      break;
    }
    return jml;
  }

  /**
   * pending interactions that can be cancelled
   */
  var pending = [],
    useBookmarkDefault = false;

  /**
   * Fire a custom "bookmark" event so that Client can react.
   */
  function bookmarked(anchor, data) {
    if (anchor.dispatchEvent) {
      var msg;
      try {
        msg = anchor.ownerDocument.createEvent('CustomEvent');
        msg.initCustomEvent('bookmark', true, false, data);
      } catch (e) {
        msg = anchor.ownerDocument.createEvent('HTMLEvents');
        msg.initEvent('bookmark', true, false);
      }
      anchor.dispatchEvent(msg);
    }
  }

  function bookmarkDefault(anchor) {
    var doc = anchor.ownerDocument,
      dialog = doc.createElement('div'),
      offsets = locate(anchor),
      href = anchor.href,
      popular = [ {
        alt: 'Facebook',
        icon: '' +
              'AAAIAAAAAEABAAAAAAAgAAAABILAAASCwAAEAAAABAAAACYWTsAnmJFAKt4YAC' +
              'seWEAtIRtAPTu6wD%2F%2F%2F8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
              'AAAAAAAAAAAAAd3d3d3d3d3dzAAAAAAAAN3FERERGZEQXcUREREZkRBdwAAAAB' +
              'mAAB3AAAAAGYAAHcAAAAAZgAAdwAAAFVmVQB3AAAAZmZmAHcAAAAAZgAAdwAAA' +
              'ABmAAB3AAAAAFZmAHcAAAAAJWYAdwAAAAAAAAB3MAAAAAAAA3d3d3d3d3d3f%2' +
              'F%2FwAAgAEAAIABAACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAAgAEAAIA' +
              'BAACAAQAAgAEAAIABAAD%2F%2FwAA',
        name: 'Like it!',
        window: 'http://www.facebook.com/share.php?u='
      }, {
        alt: 'Twitter',
        icon: '' +
              'AAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAD%2F%2F%2F8A%2F%2' +
              'F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F' +
              '%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%' +
              '2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2' +
              'FAP%2F%2F%2FwD%2F%2F%2F8A%2Fv7%2BD%2F7%2B%2Fj%2F%2B%2Fv5g%2Fv7' +
              '%2BYP7%2B%2FmD%2B%2Fv5I%2Fv7%2BKP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%' +
              '2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2B%2Fv4' +
              'H%2Fv7%2BUPbv4pHgx47B1K9Y3tWwWN7Ur1je3sKCx%2BrbuKj%2B%2Fv5n%2F' +
              'v7%2BGP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2B%2Fv4' +
              'Y%2BfbweM2ycMe2iB7%2FvI0f%2F8STIf%2FKlyL%2FzJki%2F8yZIv%2FLmCL' +
              '%2F0ahK5%2FHp1JH%2B%2Fv4Y%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A7' +
              'OTTaquHN%2BCujkXPs5ZTv6N6G%2F%2B2iB7%2FxpUh%2F8yZIv%2FMmSL%2Fz' +
              'Jki%2F8yZIv%2FKmy738OjUi%2F%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FA' +
              'MKtfY7w6%2BEf%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A3sqbp8iWIf%2F' +
              'MmSL%2FzJki%2F8yZIv%2FMmSL%2Fy5gi%2F8mePO7%2B%2Fv4w%2F%2F%2F%2' +
              'FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2B%2Fv4H%2F' +
              'v7%2BV9CtWN3KmCL%2FzJki%2F8yZIv%2FMmSL%2FzJki%2F8yZIv%2FJlyH%2' +
              'F5tSqp%2F7%2B%2FmD%2B%2Fv4%2F%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2' +
              'F8A%2BPXvJtGyZdXNnS%2F3y5gi%2F8qYIv%2FLmCL%2FzJki%2F8yZIv%2FMm' +
              'SL%2Fy5gi%2F82iPO7LqVfe0byMmf%2F%2F%2FwD%2F%2F%2F8A%2Fv7%2BD%2' +
              'FDo1JHKmy73ypci%2F8KSIP%2B%2FjyD%2FxpQh%2F8uYIv%2FMmSL%2FzJki%' +
              '2F8qYIv%2B%2FjyD%2FrIEd%2F9nKqH7%2F%2F%2F8A%2F%2F%2F%2FAPPu4Tz' +
              'AlSz3wZEg%2F7mLH%2F%2BsgR3%2FuZdGz7mLH%2F%2FJlyH%2FzJki%2F8yZI' +
              'v%2FGlSH%2Fto0r9eXbxD%2FVx6dg%2F%2F%2F%2FAP7%2B%2Fh%2Fp38WhtIs' +
              'q9al%2FHP%2BkfyjuybaKgf%2F%2F%2FwCzjzjlwJAg%2F8qYIv%2FJlyH%2Fu' +
              '4wf%2F8CkYrn%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwDj2sRMnHUa%2F7m' +
              'eYa7Vx6dg%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A2MmnYK6DHf%2B%2Bj' +
              'iD%2Fvo4g%2F62CHf%2Fk2sQ%2F%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8' +
              'A8OvhH%2Ff07w%2F%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8' +
              'A%2F%2F%2F%2FAP%2F%2F%2FwC%2Fp3Cfpnwc%2F66GKvPg1LZ8%2F%2F%2F%2' +
              'FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%' +
              '2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2' +
              'F%2F%2F8A%2F%2F%2F%2FANXHp2DJtoqByLWKgf%2F%2F%2FwD%2F%2F%2F8A%' +
              '2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2' +
              'F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F' +
              '%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%' +
              '2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2' +
              'F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2F' +
              'wD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FA' +
              'P%2F%2F%2FwD%2F%2F%2F8A%2F%2F8AAP%2F%2FAADgPwAAwA8AAIAHAAB4BwA' +
              'A%2BAMAAPAAAADgAQAA4AMAAMEDAADPhwAA%2F48AAP%2FnAAD%2F%2FwAA%2F' +
              '%2F8AAA%3D%3D',
        name: 'Tweet it!',
        window: 'http://twitter.com/share?url='
      }, {
        alt: 'Digg',
        icon: '' +
              'AAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAADezr1ImmYw55BXG%2' +
              'F%2BQVxv%2FkFcb%2F5BXG%2F%2BQVxv%2FkFcb%2F5BXG%2F%2BQVxv%2FkFc' +
              'b%2F5BXG%2F%2BQVxv%2FkFcb%2F5pmMOfezr1Im2gy5ZFYHf%2BRWR7%2FkVk' +
              'e%2F5FZHv%2BRWR7%2FkVke%2F5FZHv%2BRWR7%2FkVke%2F5FZHv%2BRWR7%2' +
              'FkVke%2F5FZHv%2BRWB3%2Fm2gy5ZBXG%2F%2BSWh%2F%2Fklof%2F5JaH%2F%' +
              '2BSWh%2F%2Fklof%2F5JaH%2F%2BSWh%2F%2Fklof%2F5JaH%2F%2BSWh%2F%2' +
              'Fklof%2F5JaH%2F%2BSWh%2F%2Fklof%2F5BXG%2F%2BQVxv%2Fk1sg%2F5NbI' +
              'P%2BTWyD%2Fk1sg%2F5NbIP%2BTWyD%2Fk1sg%2F5NbIP%2BTWyD%2Fk1sg%2F' +
              '5NbIP%2BTWyD%2Fk1sg%2F5NbIP%2BQVxv%2FkFcb%2F5NcIv%2BTXCL%2Fk1w' +
              'i%2F5NcIv%2BTXCL%2Fk1wi%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F' +
              '%2F%2F%2F%2F5NcIv%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F' +
              '%2F%2BTXCL%2FkFcb%2F5BXG%2F%2BVXiX%2FlV4l%2F5VeJf%2BVXiX%2FlV4' +
              'l%2F5VeJf%2BVXiX%2FlV4l%2F%2F%2F%2F%2F%2F%2BVXiX%2FlV4l%2F5VeJ' +
              'f%2F%2F%2F%2F%2F%2FlV4l%2F5BXG%2F%2BQVxv%2F%2F%2F%2F%2F%2F%2F%' +
              '2F%2F%2F%2F%2F%2F%2F%2F%2F%2FlmAn%2F%2F%2F%2F%2F%2F%2BWYCf%2F%' +
              '2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FlmAn%2F%2F%2F%2' +
              'F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F5ZgJ%2F%2BQVxv%2FkFcb%' +
              '2F%2F%2F%2F%2F%2F%2BXYin%2F%2F%2F%2F%2F%2F5diKf%2F%2F%2F%2F%2F' +
              '%2Fl2Ip%2F%2F%2F%2F%2F%2F%2BXYin%2F%2F%2F%2F%2F%2F5diKf%2F%2F%' +
              '2F%2F%2F%2Fl2Ip%2F%2F%2F%2F%2F%2F%2BXYin%2FkFcb%2F5BXG%2F%2F%2' +
              'F%2F%2F%2F%2FmGMr%2F%2F%2F%2F%2F%2F%2BYYyv%2F%2F%2F%2F%2F%2F5h' +
              'jK%2F%2F%2F%2F%2F%2F%2FmGMr%2F%2F%2F%2F%2F%2F%2BYYyv%2F%2F%2F%' +
              '2F%2F%2F5hjK%2F%2F%2F%2F%2F%2F%2FmGMr%2F5BXG%2F%2BQVxv%2F%2F%2' +
              'F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FmWQt%2F%2F%2F%2F%2F' +
              '%2F%2BZZC3%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2Fm' +
              'WQt%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F5lkLf%2B' +
              'QVxv%2FkFcb%2F5pmMP%2BaZjD%2F%2F%2F%2F%2F%2F5pmMP%2BaZjD%2FmmY' +
              'w%2F5pmMP%2BaZjD%2FmmYw%2F5pmMP%2BaZjD%2FmmYw%2F5pmMP%2BaZjD%2' +
              'FkFcb%2F5BXG%2F%2BbZzH%2Fm2cx%2F%2F%2F%2F%2F%2F%2BbZzH%2F%2F%2' +
              'F%2F%2F%2F5tnMf%2BbZzH%2Fm2cx%2F5tnMf%2BbZzH%2Fm2cx%2F5tnMf%2B' +
              'bZzH%2Fr4Va%2F5BXG%2F%2BQVxv%2FnGkz%2F5xpM%2F%2BcaTP%2FnGkz%2F' +
              '5xpM%2F%2BcaTP%2FnGkz%2F5xpM%2F%2BcaTP%2FnGkz%2F5xpM%2F%2BcaTP' +
              '%2FnGkz%2F5xpM%2F%2BQVxv%2FkFcb%2F5xpNP%2BcaTT%2FnGk0%2F5xpNP%' +
              '2BcaTT%2FnGk0%2F5xpNP%2BcaTT%2FnGk0%2F5xpNP%2BcaTT%2FnGk0%2F5x' +
              'pNP%2BcaTT%2FkFcb%2F5pmMOeZZC3%2FnWs2%2F51rNv%2Bdazb%2FnWs2%2F' +
              '51rNv%2Bdazb%2FnWs2%2F51rNv%2Bdazb%2FnWs2%2F51rNv%2Bdazb%2FmWQ' +
              't%2F5pmMOfj1cY%2Bp3pLyZBXG%2F%2BQVxv%2FkFcb%2F5BXG%2F%2BQVxv%2' +
              'FkFcb%2F5BXG%2F%2BQVxv%2FkFcb%2F5BXG%2F%2BQVxv%2FkFcb%2F6d6S8n' +
              'j1cY%2BgAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
              'AAAAAAAAAAAAAAAAAAAAAAAAAgAEAAA%3D%3D',
        name: 'Digg it!',
        window: 'http://digg.com/submit?url='
      }, {
        alt: 'StumbleUpon',
        icon: '' +
              'AAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM' +
              'AAAANAAAAIAAAADoAAABXAAAAbwAAAHkAAAB5AAAAbQAAAFYAAAA5AAAAIAAAA' +
              'A0AAAADAAAAAAAAAAEAAAANAAAAKQAAAFFjOQCdk1QA95JJAP%2BSSgD%2Fcjg' +
              'A6UAgAKcAAACOAAAAdQAAAFAAAAApAAAADQAAAAAAAAAGAAAAH4dTAZSdWgD6n' +
              'lYA%2F6JaAP%2BjXAD%2Fo1sA%2F6JZAP%2BcUwD%2FiEUA9UolAKcAAACBAAA' +
              'AUQAAACAAAAABAAAADp12H427cwH%2Ft24A%2F7ZuAP%2B4bgD%2FuG8A%2F7d' +
              'uAP%2B4bwD%2FqmIA%2F5tSAP%2BNSQD4SiUApwAAAHUAAAA5AAAABAAAABrCk' +
              'Sf1uYMX%2F7R%2FEf%2BobgH%2Fm1QA%2F6plAP%2BlXgD%2FrWgA%2F8SHDf%' +
              '2FDixf%2FoFwA%2F4JBAPUAAACPAAAAVgAAAAbb29iJ%2Fv77%2F%2F7%2B%2F' +
              'P%2F%2B%2Fvz%2F%2Fv34%2F%2B7amP%2BkWQH%2FpVkA%2F%2B3Qf%2F%2F%2' +
              'B%2Ffb%2F%2Ff36%2F%2FPpv%2F%2BybAP%2FSSUApwAAAG0AAAAIAZABiym%2' +
              'BGv8nuxf%2FIrUU%2F3PcZv%2F%2B%2Fv3%2FqHYQ%2F7J3CP%2F%2B%2FfX%2' +
              'FjN2C%2F0rKPP%2F7%2Fvn%2F2rdv%2F3xDAOkAAAB4AAAACAzFDP8BrAH%2FM' +
              'cYl%2F4Thdv%2FM%2BMT%2F%2Fv76%2F614Ff%2B0gB%2F%2F%2Fv7%2B%2Fzb' +
              'ELP8AswD%2F7%2F3q%2F%2BzSof%2BYVAD%2FAAAAdwAAAAcLwgr%2FGLYP%2F' +
              '%2Fj%2B9f%2F99%2BP%2F9uOv%2F9mxWf%2BWUgD%2FrXsg%2F%2F7%2B%2Fv8' +
              '1wCj%2FAKwA%2F%2B796v%2FozqL%2FlVIA%2FwAAAGsAAAAFB7gHwTXFLP%2F' +
              '%2B%2Fv7%2F16xT%2F7NuCv%2BpZQX%2FqmYG%2F8KbTv%2F%2B%2Fv7%2FJ7o' +
              'a%2FwCsAP%2Fw%2Fer%2F582i%2F247AN4AAABTAAAAAgGgAYQFvQP%2FyvS%2' +
              'F%2F%2F7%2B%2BP%2F9%2B%2Bz%2F%2Ffvs%2F%2F387f%2F%2B%2Fvz%2F3Pv' +
              'P%2FwO3Av8BtAD%2F7%2F3q%2F%2BbNov9cLwCaAAAAMwAAAAEs2Cc4G9kX9QL' +
              'FAf8jwRP%2FLsYf%2FzHJIf8wySH%2FMswi%2FwjJA%2F8AwQD%2FALwA%2F%2' +
              'FD96%2F%2FDspTpAAAAPAAAABUAAAAAAAAAASvUJzki6yL4C9oL%2FwXUBf8B0' +
              'AH%2FANAA%2FwDRAP8AzwD%2FAM8A%2FwG7AP%2Fk8d74AAAAPQAAABcAAAAFA' +
              'AAAAAAAAAAAAAABK9QnOTHxLPUZ6xn%2FDt8O%2FwnZCf8C1AL%2FANEA%2FwD' +
              'SAP8CyAD1AAAAMwAAABYAAAAGAAAAAQAAAAAAAAAEAAAAAAAAAAEs2Cc4EtgSh' +
              'CncI%2BcT6BP%2FDN8L%2Fw3UDcIKvQmKAAAAHAAAAA4AAAAEAAAAAQAAAAAAA' +
              'AAAAAAAAAAAAAAAAAAAAAAAAQAAAAMAAAAGAAAACAAAAAkAAAAJAAAABwAAAAQ' +
              'AAAABAAAAAAAAAAAAAAAA%2F%2F8AAP5%2FAAD4HwAA4A8AAOAHAADAAwAAwAM' +
              'AAIABAACAAQAAwAMAAMADAADgBwAA8A8AAPgfAAD%2BfwAA%2F%2F8AAA%3D%3D',
        name: 'Thumbs up!',
        window: 'http://www.stumbleupon.com/submit?url='
      } ];

    // List the popular defaults.
    iterate(popular, function (provider) {
      var option = doc.createElement('a');
      option.style.display = 'block';
      option.style.height = '16px';
      option.style.padding = '2px 0px 2px 20px';
      option.style.backgroundImage = 'url("' + provider.icon + '")';
      option.style.backgroundRepeat = 'no-repeat';
      option.style.font = 'menu';
      option.appendChild(doc.createTextNode(provider.name));
      option.setAttribute('title', provider.alt +
          (provider.description ? ': ' + provider.description : ''));
      option.setAttribute('href', provider.window + encodeURIComponent(href));
      option.setAttribute('target', '_blank');
      option.onfocus = function () {
        option.style.backgroundColor = '#ffa';
      };
      option.onblur = function () {
        option.style.backgroundColor = 'transparent';
      };
      option.onmouseover = function () {
        option.focus();
      };
      option.onkeydown = function (e) {
        e = e || window.event;
        if (40 === e.keyCode) {
          if (option.nextSibling) {
            option.nextSibling.focus();
          }
          e.returnValue = false;
          if (e.preventDefault) {
            e.preventDefault();
          }
        } else if (38 === e.keyCode) {
          if (option.previousSibling) {
            option.previousSibling.focus();
          }
          e.returnValue = false;
          if (e.preventDefault) {
            e.preventDefault();
          }
        } else if (27 === e.keyCode) {
          dialog.parentNode.removeChild(dialog);
        }
      };
      option.onclick = function () {
        bookmarked(anchor, null);
      };
      dialog.appendChild(option);
    });

    dialog.style.backgroundColor = 'white';
    dialog.style.color = 'black';
    dialog.style.border = 'thin outset black';
    dialog.style.position = 'absolute';
    dialog.style.top = (offsets.top + anchor.offsetHeight) + 'px';
    dialog.style.left = offsets.left + 'px';
    anchor.parentNode.appendChild(dialog);
    dialog.firstChild.focus();
    pending.push(makeNodeRemover(dialog));
  }

  /**
   * A social bookmarking API.
   */
  function bookmark(anchor, later) {
    useBookmarkDefault = false;
    var wanted = [ 'http://web-send.org/bookmark/' ],
      mimeParser = /^([\w!#\$%&'*+-\.\^`|~]*)\/([\w!#\$%&'*+-\.\^`|~]*)(;|$)/,
      mimeType = mimeParser.exec(anchor.getAttribute('type') || ''),
      dialog = anchor.ownerDocument.createElement('iframe');
    dialog.sandbox = 'allow-same-origin allow-forms allow-scripts';
    dialog.style.display = 'none';
    anchor.parentNode.appendChild(dialog);
    pending.push(makeNodeRemover(dialog));
    if (mimeType) {
      wanted.push(wanted[0] + '#' + mimeType[1].toLowerCase() + '/');
      wanted.push(wanted[1] + mimeType[2].toLowerCase());
    }
    window.introducer.connect(anchor, dialog, wanted,
                              function (service, agreed, framed) {
        if (!service) {
          // no matching service
          makeNodeRemover(dialog)();
          useBookmarkDefault = true;
          if (!later) {
            bookmarkDefault(anchor);
          }
          return;
        }
        pending.push(function () { service.close(); });
        if (framed) {
          var offsets = locate(anchor);
          dialog.style.display = 'block';
          dialog.style.zIndex = 1;
          dialog.style.borderWidth = 3;
          dialog.style.position = 'absolute';
          dialog.style.top = (offsets.top + anchor.offsetHeight) + 'px';
          dialog.style.left = offsets.left + 'px';
          dialog.width = 500;
          dialog.height = 100;
          dialog.frameBorder = 0;
          dialog.scrolling = 'no';
        }

        // Send a description of the link element to the selected Service.
        service.postMessage([ asJSONML(anchor) ]);
        service.onmessage = function (msg) {
          service.close();
          makeNodeRemover(dialog)();
          bookmarked(anchor, msg.data);
        };
      });
  }

  function initiateIntroduction(e) {
    e = e || window.event;

    // On deliberate user input, dismiss any pending interactions.
    if ('click' === e.type) {
      while (pending.length) {
        pending.pop()();
      }
    }

    // Determine if user action triggers a new introduction.
    var target = e.target || e.srcElement;
    while ('A' !== target.tagName) {
      target = target.parentNode;
      if (!target) {
        return;
      }
    }
    if (!target.rel) {
      return;
    }
    return iterate(target.rel.split(' '), function (rel) {
      if (/^bookmark$/i.test(rel)) {
        if ('click' === e.type) {
          if (1 === (e.button || 1) &&
              !e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) {
            e.returnValue = false;
            if (e.preventDefault) {
              e.preventDefault();
            }
            bookmark(target);
          }
        } else {
          if (!useBookmarkDefault) {
            bookmark(target, true);
          }
        }
        return true;
      }
    });
  }
  if (window.addEventListener) {
    window.addEventListener('click', initiateIntroduction, false);
  } else if (window.document.attachEvent) {
    window.document.attachEvent('onclick', initiateIntroduction);
  }
  if (window.introducer.isSimulated) {
    // Prepare an introduction click target in a preceeding mouseover event.
    if (window.addEventListener) {
      window.addEventListener('mouseover', initiateIntroduction, false);
    } else if (window.document.attachEvent) {
      window.document.attachEvent('onmouseover', initiateIntroduction);
    }
  }
}());
